package com.nulldev.util.web.nTube;

import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.CONNECTIONS_KEEPALIVE;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.CONSTANT_CONNECTION;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.DISABLE_MTE;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.DISABLE_PRECACHING;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.DISABLE_SSL;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.ENABLE_INVIDIOUS_COMPAT;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.EXPOSE_LOCAL_IPs;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.INSTANCE_INFO_PATH;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.MODE;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.OPENJSSE_PATH;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.OPEN_PORTS;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.PRINT_EXPERIMENTS;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.RUN_PRIV_PORTS;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.SSL_CERTIFICATE_PATH;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.SSL_USE_ECDSA_PRIVATE_KEY;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.THROTTLING;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.TRACE_AS_DEBUG;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.USE_SINGLE_THREAD;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments._SAPI_ENGINES;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments._disableGC;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments._trace;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.port;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.useIPv6;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.useNFEE;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.ytAPI;
import static com.nulldev.util.web.nTube.NTubeLaunchArguments.nTubeArguments.ytAPIDebug;

import java.lang.reflect.Field;
import java.security.Provider;
import java.security.Security;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.Arguments.ArgumentInit;
import com.nulldev.util.JVM.Arguments.ParsedArguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.arguments.Argument;
import com.nulldev.util.JVM.arguments.ArgumentWithValue;
import com.nulldev.util.JVM.reflections.dynamicLoader.DynamicLoader;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.concurrency.threadIt.v4.debugger.threadItUnwinder;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.utils.threadit_on_jre.TIOnJRE;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.languages.json.uniapi.JSONAPI;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.nullUtil;
import com.nulldev.util.manage.experiments.Experiments;
import com.nulldev.util.networking.misc.TLSUtil;
import com.nulldev.util.scripting.ScriptAPI.nScripting;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.URLUtils.nStreamHandlerFactory;
import com.nulldev.util.web.nTubeExtensions.sponsorFree.SponsorBlockAPI;

public class NTubeLaunchArguments {

	private static final Logger log = LoggerFactory.getLoggerD(true);

	private static final Argument help = new Argument(new String[]
		{ "-h", "--help" }, "Display this text and exit.", true);
	private static volatile ParsedArguments parsed;

	public static ParsedArguments get() {
		return parsed;
	}

	public static class nTubeArguments {
		// TODO: Update this

		public static final ArgumentWithValue<Integer> port = new ArgumentWithValue.IntegerArgument(new String[]
			{ "--NTubeServer.usePort", "-NTubeServer.usePort", "--port" }, "Sets the default port to be used",
				"A valid unused port. (Ports below 1000 require admin/root!)", true);

		public static final ArgumentWithValue<Boolean> RUN_PRIV_PORTS = new ArgumentWithValue.BooleanArgument(new String[]
			{ "--NTubeServer.enablePrivPorts", "-NTubeServer.enablePrivPorts", "--enablePrivPorts" }, "If enabled, allows running as root/admin.",
				"A boolean value (true/false)", true);

		public static final Argument _trace = new Argument(new String[]
			{ "--trace", "-trace" }, "Logger API: Enables tracing and debugging level logging.", true);
		public static final Argument useIPv6 = new Argument(new String[]
			{ "--NTubeServer.useIPv6", "-NTubeServer.useIPv6" }, "Tells Java to prefer resolving IPv6 addresses.", true);
		public static final Argument _disableGC = new Argument(new String[]
			{ "--NTubeServer.disableBetterGC", "-NTubeServer.disableBetterGC" }, "Disables tweaks used to reduce RAM usage by nTube's APIs.", true);
		public static final Argument useNFEE = new Argument(new String[]
			{ "--NTubeServer.useNFEE", "-NTubeServer.useNFEE" }, "Tells nTube to use the NFEE extractor. (NeXt Fast Execution [Page] Extractor)", true);
		public static final Argument ytAPI = new Argument(new String[]
			{ "--NTubeServer.useYTAPI", "-NTubeServer.useYTAPI" }, "Tells nTube to use the YTAPI extractor. (YouTube [Internal] API Extractor)", true);
		public static final Argument ytAPIDebug = new Argument(new String[]
			{ "--YouTubeExtractor.debug", "-YouTubeExtractor.debug" }, "YouTubeExtractor API: Enables debug level logging.", true);
		public static final Argument _SAPI_ENGINES = new Argument(new String[]
			{ "--NTubeServer.printScriptEngines", "-NTubeServer.printScriptEngines" }, "nTube prints the supported ScriptEngines and then exits.", true);
		public static final Argument DISABLE_SSL = new Argument("--NTubeServer.noSSL", "Tells HttpClient3 to not use SSL.", true);
		public static final Argument DISABLE_PRECACHING = new Argument("--NTube.disablePrecaching", "Disables SignatureExtractor caching until it is required.",
				true);
		public static final Argument TRACE_AS_DEBUG = new Argument("--Logger.traceAsDebug", "Logger API: Trace level becomes equal to Debug level.", true);
		public static final Argument OPEN_PORTS = new Argument(new String[]
			{ "--open", "-open" }, "Makes nTube bind to your internet connection, allowing an instance to be hosted.", true);
		public static final Argument USE_SINGLE_THREAD = new Argument("--NTubeServer.avoidMainSingleThread",
				"nTube uses a seperate thread instead of the main-thread. (Does not apply to the Task API)", true);
		public static final Argument CONSTANT_CONNECTION = new Argument("--NTubeServer.disableConstantConnection",
				"HttpServer3 closes sockets on connection finish, this may increase the connection count.", true);
		public static final Argument CONNECTIONS_KEEPALIVE = new Argument("--HttpServer3.keepAlive", "HttpServer3 uses TCP's keepAlive feature.", true);
		public static final Argument DISABLE_MTE = new Argument(new String[]
			{ "--NTubeServer.disableMTE", "-NTubeServer.disableMTE" }, "Forces nTube to use the single-threaded extractors for content.", true);
		public static final Argument EXPOSE_LOCAL_IPs = new Argument("--NTubeServer.exposeIPs", "Adds raw URLs for debugging purposes.", true);
		public static final ArgumentWithValue<String> OPENJSSE_PATH = new ArgumentWithValue.StringArgument(new String[]
			{ "--NTubeServer.openJSSEPath", "-NTubeServer.openJSSEPath" }, "The path to the jar of the OpenJSSE library. (Java >=8 only at the moment)",
				"A path to the jar file.", true);
		public static final Argument PRINT_EXPERIMENTS = new Argument("--NTubeServer.printExperiments", "Prints all present experiments in nullUtil.", true);
		public static final ArgumentWithValue<Integer> THROTTLING = new ArgumentWithValue.IntegerArgument(new String[]
			{ "--NTubeServer.throttleDownstream", "-NTubeServer.throttleDownstream" },
				"Allows nTube to throttle bandwidth given to clients to avoid network saturation.", "Bandwidth limit in bits per second.", true);
		public static final ArgumentWithValue<String> INSTANCE_INFO_PATH = new ArgumentWithValue.StringArgument(new String[]
			{ "--NTubeServer.instanceInfoPath", "-NTubeServer.instanceInfoPath" },
				"The path to the instance info file containing a config of the instance information", "A path to the instance info file.", true);

		public static final ArgumentWithValue<String> SSL_CERTIFICATE_PATH = new ArgumentWithValue.StringArgument(new String[]
			{ "--NTubeServer.sslCertPath", "-NTubeServer.sslCertPath" }, "Allows nTube to use a proper SSL certificate.",
				"A PEM based certificate file containing both the cert and private key OR a semi-colon seperated list of cert-pem and priv-key-pem.", true);
		public static final ArgumentWithValue<Boolean> SSL_USE_ECDSA_PRIVATE_KEY = new ArgumentWithValue.BooleanArgument(new String[]
			{ "--NTubeServer.sslEC", "-NTubeServer.sslEC", "--NTubeServer.sslECDSA", "-NTubeServer.sslECDSA" },
				"Tells nTube to try loading a ECDSA private key instead of an RSA one.", "boolean value", true);

		public static final ArgumentWithValue<Boolean> ENABLE_INVIDIOUS_COMPAT = new ArgumentWithValue.BooleanArgument(new String[]
			{ "--NTubeServer.enableInvidiousCompat", "-NTubeServer.enableInvidiousCompat", "--invidious", "-invidious" },
				"Enables an experimental compat. layer intended for Invidious clients.", "boolean value", true);

		public static final Argument VQM_LOW_THROUGHPUT = new Argument(new String[]
			{ "--VideoQueueMode.low", "--nTube.lowQueue", "-nTube.lowQueue" }, "Adds rate limiting for videos. (low throughput, temporary option)", true);
		public static final Argument VQM_MEDIUM_THROUGHPUT = new Argument(new String[]
			{ "--VideoQueueMode.medium", "--nTube.mediumQueue", "-nTube.mediumQueue" }, "Adds rate limiting for videos. (medium throughput, temporary option)",
				true);
		public static final Argument VQM_HIGH_THROUGHPUT = new Argument(new String[]
			{ "--VideoQueueMode.high", "--nTube.highQueue", "-nTube.highQueue" }, "Adds rate limiting for videos. (high throughput, temporary option)", true);
		public static final Argument VQM_CUSTOM = new Argument(new String[]
			{ "--VideoQueueMode.custom", "--nTube.customQueue", "-nTube.customQueue" }, "Adds rate limiting for videos. (custom, temporary option)", true);

		public static final ArgumentWithValue<Integer> VQM_CUSTOM_SIZE = new ArgumentWithValue.IntegerArgument(new String[]
			{ "--VideoQueueMode.customSize", "--nTube.customQueueSize", "-nTube.customQueueSize" }, "Sets the size of the queue. (temporary option)",
				"(integer value, more than zero)", true);
		public static final ArgumentWithValue<Integer> VQM_CUSTOM_DELAY = new ArgumentWithValue.IntegerArgument(new String[]
			{ "--VideoQueueMode.customDelay", "--nTube.customQueueDelay", "-nTube.customQueueDelay" },
				"Sets the delay on issuing operations. (temporary option)", "(integer value, more than zero)", true);

		public static final ArgumentWithValue<String> MODE = new ArgumentWithValue.StringArgument(new String[]
			{ "--mode", "-m", "--nTube.mode", "-nTube.mode" }, "Sets the mode in which nTube is configured, defaults to FULL.", "A valid string option", true);
	}

	public static NTubeOperationMode getOperationMode(final ParsedArguments pa) {
		NTubeOperationMode opMode = NTubeOperationMode.DEFAULT;

		if (pa.has(MODE)) {
			final String val = pa.getValue(MODE);
			opMode = NTubeOperationMode.getFromString(val);
		}

		return opMode;
	}

	public static void init(final String[] _args) {
		Arguments.argumentInit(new ArgumentInit() {

			@Override
			public void onInit(final Arguments args) {
				args.addArgument(MODE);
				args.addArgument(port);
				args.addArgument(help);
				args.addArgument(_trace);
				args.addArgument(useIPv6);
				args.addArgument(JSONAPI.ARGUMENT);
				args.addArgument(ytAPIDebug);
				args.addArgument(DISABLE_MTE);
				args.addArgument(useNFEE);
				args.addArgument(ytAPI);
				args.addArgument(_SAPI_ENGINES);
				args.addArgument(DISABLE_SSL);
				args.addArgument(DISABLE_PRECACHING);
				args.addArgument(TRACE_AS_DEBUG);
				args.addArgument(OPEN_PORTS);
				args.addArgument(USE_SINGLE_THREAD);
				args.addArgument(CONSTANT_CONNECTION);
				args.addArgument(CONNECTIONS_KEEPALIVE);
				args.addArgument(EXPOSE_LOCAL_IPs);
				args.addArgument(OPENJSSE_PATH);
				args.addArgument(RUN_PRIV_PORTS);
				args.addArgument(PRINT_EXPERIMENTS);
				args.addArgument(THROTTLING);
				args.addArgument(INSTANCE_INFO_PATH);
				args.addArgument(SSL_CERTIFICATE_PATH);
				args.addArgument(SSL_USE_ECDSA_PRIVATE_KEY);
				args.addArgument(SponsorBlockAPI.API_FRONTEND);
				args.addArgument(ENABLE_INVIDIOUS_COMPAT);
				args.addArgument(nTubeArguments.VQM_LOW_THROUGHPUT);
				args.addArgument(nTubeArguments.VQM_MEDIUM_THROUGHPUT);
				args.addArgument(nTubeArguments.VQM_HIGH_THROUGHPUT);
				args.addArgument(nTubeArguments.VQM_CUSTOM);
				args.addArgument(nTubeArguments.VQM_CUSTOM_SIZE);
				args.addArgument(nTubeArguments.VQM_CUSTOM_DELAY);
			}
		});
		log.debug("JVM Version: " + JVM.version() + " [string: " + JVM.versionString() + "]");
		nullUtil.isSupportedJVM();
		if (JVM.version() < 1.8f) {
			log.error("Invalid JVM version! Exiting...");
			JVM.println("nTube bailed out as it did NOT meet all requirments.");
			JVM.println("Missing Requirement:");
			JVM.println("\tJVM >= 1.8");
			FastIO.get().flush();
			System.exit(-1);
		}
		TIOnJRE.apply();
		if (JVM.version() >= 11f) {
			if (HttpClient.ENABLE_NATIVE_MODE) {
				log.info("Enabled HttpClient's NATIVE mode.");
			} else {
				// MISC: Get this shit fixed in 4.0-quantum using require("HttpClient4.new")
				log.warn("HttpClient's NATIVE mode was disabled.");
				if (JVM.version() <= 16f) {
					log.warn(
							"To enable you need to prepend the following JVM arguments: --add-exports java.net.http/jdk.internal.net.http=ALL-UNNAMED --illegal-access=warn");
				} else {
					log.error("Due to the JVM version being used, NATIVE mode CANNOT be enabled.");
				}
			}
		}
		parsed = Arguments.setup(_args);
		if (parsed.has(help)) {
			Arguments.sysPrintHelp();
			FastIO.get().flush();
			System.exit(-1);
			return;
		}
		if (parsed.has(PRINT_EXPERIMENTS)) {
			Experiments.listAllExperiments();
		}
		if (parsed.has(useIPv6)) {
			System.setProperty("java.net.preferIPv6Addresses", "true");
			log.info("Preferring IPv6 addresses.");
		}
		if (parsed.has(_disableGC)) {
			log.warn("launchArguments() -> Disabled GC tweaks.");
		}
		if (parsed.has(SponsorBlockAPI.API_FRONTEND)) {
			log.warn("launchArguments() -> Using '" + parsed.getValue(SponsorBlockAPI.API_FRONTEND) + "' as the SponsorBlock backend.");
		}
//		if (parsed.has(useMTPage)) {
//			log.warn("launchArguments() -> Using experimental multi-threaded page extractor.");
//		}
		if (Arguments.hasArgument("--HttpServer3.constantConnection")) {
			log.warn("launchArguments() -> HttpServer3 will attempt to maintain a single HTTP(S) connection.");
		}
		if (Arguments.hasArgument("--HttpServer3.keepAlive")) {
			log.warn("launchArguments() -> HttpServer3 will attempt to prevent connection loss.");
		}
		if (Arguments.hasArgument("--threadIt.enableUnwinder")) {
			new Thread(new Runnable() {

				@Override
				public void run() {
					threadItUnwinder.main(_args);
				}
			}, "tUnwinder").start();
		}
		if (parsed.has(useNFEE)) {
			log.warn("Using NFEE over Standard, things may break.");
		} /*
			 * else if (parsed.has(useNFEE_MT)) {
			 * log.warn("Using NFEE-MT over Standard, things may break."); }
			 */else if (parsed.has(ytAPI)) {
			log.warn("Using YTAPI over Standard, things may break.");
		}
		if (parsed.has(_SAPI_ENGINES)) {
			log.info("Printing installed script engines via nScripting.");
			ArrayUtils.printLln(nScripting.engines());
		}
		if (parsed.has(DISABLE_MTE)) {
			log.warn("Disabling all multi-threaded extractors!");
		}
		if (parsed.has(OPENJSSE_PATH)) {
			log.info("Path provided, loading OpenJSSE...");
			final boolean supportsTLS13 = TLSUtil.allowTLS13() && Arguments.hasArgument("--NTubeServer.allowTLS13");
			if (JVM.version() <= 1.8f && supportsTLS13) {
				log.warn("This runtime environment already supports TLS 1.3, skipping OpenJSSE.");
				System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");
			} else if (JVM.version() <= 1.8f) {
				final String path = (String) parsed.getValue(OPENJSSE_PATH);
				if (FileUtil.isFile(path)) {
					try {
						DynamicLoader.addFile(path);
					} catch (Exception e) {
						log.error("Failed to load library!", e);
					}
					try {
						final ClassLoader syscls = ClassLoader.getSystemClassLoader();
						final Class<?> lib_cls = syscls.loadClass("org.openjsse.Version");
						final Field f = lib_cls.getField("version");
						f.setAccessible(true);
						final String lib_version = (String) f.get(String.class);
						log.info("OpenJSSE, version " + lib_version + ".");
						final Class<?> cn = syscls.loadClass("org.openjsse.net.ssl.OpenJSSE");
						final Provider pv = (Provider) cn.newInstance();
						final int loc = Security.insertProviderAt(pv, 1); // was 4.
						log.info("Installed OpenJSSE into position " + loc + ".");
						final SSLContext ssc = SSLContext.getInstance("TLS", pv);
						final TrustManager[] trustAllCerts = new TrustManager[]
							{ new X509TrustManager() {
								@Override
								public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
								}

								@Override
								public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
								}

								@Override
								public java.security.cert.X509Certificate[] getAcceptedIssuers() {
									return null;
								}
							} };
						ssc.init(null, trustAllCerts, null);
						SSLContext.setDefault(ssc);
						System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");
					} catch (Exception e) {
						log.error("Failed to init library!", e);
					}
				} else {
					log.error("File not found: " + path);
				}
			} else {
				log.warn("Incompatible runtime! [using: " + JVM.version() + "]");
			}
		} else if (JVM.version() >= 11f) {
			System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3");
		} else {
			System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
		}
		log.debug("HttpClient Request Backend: " + HttpClient.getStandardBackend());
		log.debug("HttpClient Fallback Backend: " + HttpClient.getLegacyBackend());
		try {
			nStreamHandlerFactory.install();
		} catch (Exception e) {
			log.error("Failed to install stream handler!", e);
		}
		Experiments.printActiveExperiments();
		if (ExecutorManager.hasCustomServices()) {
			ExecutorManager.get().setExecutor(ExecutorManager.getService());
		}
		if (Experiments.get("ntube://disable-reflective-warnings/").isActive()) {
			ExecutorManager.globalSubmit(() -> {
				JVM.disableAccessWarnings();
				return null;
			});
		}
		if (Experiments.get("ntube://enable-dns-tweaks/").isActive()) {
			try {
				java.security.Security.setProperty("networkaddress.cache.ttl", "65");
				java.security.Security.setProperty("networkaddress.cache.negative.ttl", "5");
			} catch (Exception e) {
			}
		}
		log.debug("[debug] Global Executor: " + ExecutorManager.get().executor());
		log.debug("[debug] Global AsyncScheduler: " + ExecutorManager.get().scheduler());
		if (ExecutorManager.get().isWorkingAroundBatching()) {
			log.debug("executor.BATCH_ENABLED: NO (working around broken batching in executor)");
		} else
			log.debug("executor.BATCH_ENABLED: " + (ExecutorManager.get().supportsNativeBatching() ? "NATIVE" : ExecutorManager.ENABLE_BATCH_OPERATIONS));
	}
}
